package com.jfl.utils;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.NoHttpResponseException;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpCoreContext;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;

/**
 * <一句话功能简述>
 * 
 * @author 赵炎
 * @version [V1.00, 2019年4月23日]
 * @see [相关类/方法]
 * @since V1.00
 */
public class HttpClientUtils
{
    private static final Logger LOG = LoggerFactory.getLogger(HttpClientUtils.class);
    
    private static volatile HttpClientUtils instance = null;
    
    private static final String CHARSET_UTF8 = "UTF-8";
    
    private static final String CHARSET_GBK = "GBK";
    
//    private static final String SSL_DEFAULT_SCHEME = "https";
//    
//    private static final int SSL_DEFAULT_PORT = 443;
    
    /**
     * <默认构造函数>
     */
    private HttpClientUtils()
    {
    }
    
    /**
     * <一句话功能简述>
     * 
     * @return
     * @see [类、类#方法、类#成员]
     */
    public static HttpClientUtils getInstance()
    {
        if (instance == null)
        {
            synchronized (HttpClientUtils.class)
            {
                if (instance == null)
                {
                    instance = new HttpClientUtils();
                    return instance;
                }
            }
        }
        return instance;
    }
    
    /**
     * 相应异常处理
     * responseHandler
     */
    private ResponseHandler<String> responseHandler = new ResponseHandler<String>()
    {
        
        @Override
        public String handleResponse(HttpResponse resp)
            throws ClientProtocolException, IOException
        {
            HttpEntity entity = resp.getEntity();
            if (entity != null)
            {
                String charset = ContentType.getOrDefault(entity) == null ? "GBK" : ContentType.getOrDefault(entity).getCharset().name();
                return EntityUtils.toString(entity, charset);
            }
            return null;
        }
    };
    
    /**
     * 请求尝试
     * requestRetryHandler
     */
    private HttpRequestRetryHandler retryHandler = new HttpRequestRetryHandler()
    {
        @Override
        public boolean retryRequest(IOException ex, int count, HttpContext ctx)
        {
            if (count >= 3)
            {
                return false;
            }
            if (ex instanceof NoHttpResponseException)
            {
                return true;
            }
            HttpRequest request = (HttpRequest)ctx.getAttribute(HttpCoreContext.HTTP_REQUEST);
            if (!(request instanceof HttpEntityEnclosingRequest))
            {
                return true;
            }
            return false;
        }
    };
    
    /**
     * 构建HttpClient
     * 
     * @return
     * @see [类、类#方法、类#成员]
     */
    private CloseableHttpClient getHttpClient(String charset)
    {
        RequestConfig config = RequestConfig.custom()
            // socket 超时时间
            .setSocketTimeout(5000)
            // 连接超时时间
            .setConnectTimeout(5000)
            // 请求超时时间
            .setConnectionRequestTimeout(5000)
            .build();
        CloseableHttpClient httpClient = HttpClients.custom()
            // 设置请求参数
            .setDefaultRequestConfig(config)
            // 设置尝试尝试策略
            .setRetryHandler(retryHandler)
            .build();
        return httpClient;
    }
    
    /**
     * Get方式提交,URL中包含查询参数, 格式：http://www.g.cn?search=p&name=s.....
     *
     * @param url 提交地址
     * @return 响应消息
     */
    public String get(String url)
    {
        return get(url, null, null);
    }
    
    /**
     * Get方式提交,URL中不包含查询参数, 格式：http://www.g.cn
     *
     * @param url 提交地址
     * @param params 查询参数集, 键/值对
     * @return 响应消息
     */
    public String get(String url, Map<String, String> params)
    {
        return get(url, params, null);
    }
    
    /**
     * Get方式提交,URL中不包含查询参数, 格式：http://www.g.cn
     *
     * @param url 提交地址
     * @param params 查询参数集, 键/值对
     * @param charset 参数提交编码集
     * @return 响应消息
     */
    public String get(String url, Map<String, String> params, String charset)
    {
        if (url == null || StringUtils.isEmpty(url))
        {
            return null;
        }
        List<NameValuePair> qparams = getParamsList(params);
        if (qparams != null && qparams.size() > 0)
        {
            charset = (charset == null ? CHARSET_GBK : charset);
            String formatParams = URLEncodedUtils.format(qparams, charset);
            url = (url.indexOf("?")) < 0 ? (url + "?" + formatParams) : (url.substring(0, url.indexOf("?") + 1) + formatParams);
        }
        CloseableHttpClient httpclient = getHttpClient(charset);
        HttpGet hg = new HttpGet(url);
        hg.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:45.0) Gecko/20100101 Firefox/45.0");
        // 发送请求，得到响应
        String responseStr = null;
        try
        {
            responseStr = (String)httpclient.execute(hg, responseHandler);
        }
        catch (ClientProtocolException e)
        {
            LOG.error("客户端连接协议错误", e);
        }
        catch (IOException e)
        {
            LOG.error("IO操作异常", e);
        }
        finally
        {
            abortConnection(hg, httpclient);
        }
        return responseStr;
    }
    
    /**
     * Post方式提交,URL中不包含提交参数, 格式：http://www.g.cn
     *
     * @param url 提交地址
     * @param params 提交参数集, 键/值对
     * @return 响应消息
     */
    public String post(String url, Map<String, String> params)
    {
        return post(url, params, null);
    }
    
    /**
     * Post方式提交,URL中不包含提交参数, 格式：http://www.g.cn
     *
     * @param url 提交地址
     * @param params 提交参数集, 键/值对
     * @param charset 参数提交编码集
     * @return 响应消息
     */
    public String post(String url, Map<String, String> params, String charset)
    {
        if (url == null || StringUtils.isEmpty(url))
        {
            return null;
        }
        // 创建HttpClient实例
        CloseableHttpClient httpclient = getHttpClient(charset);
        UrlEncodedFormEntity formEntity = null;
        try
        {
            if (charset == null || StringUtils.isEmpty(charset))
            {
                formEntity = new UrlEncodedFormEntity(getParamsList(params));
            }
            else
            {
                formEntity = new UrlEncodedFormEntity(getParamsList(params), charset);
            }
        }
        catch (UnsupportedEncodingException e)
        {
            LOG.error("不支持的编码集", e);
        }
        HttpPost hp = new HttpPost(url);
        hp.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:45.0) Gecko/20100101 Firefox/45.0");
        hp.setEntity(formEntity);
        // 发送请求，得到响应
        String responseStr = null;
        try
        {
            responseStr = (String)httpclient.execute(hp, responseHandler);
        }
        catch (ClientProtocolException e)
        {
            LOG.error("客户端连接协议错误", e);
        }
        catch (IOException e)
        {
            LOG.error("IO操作异常", e);
        }
        finally
        {
            abortConnection(hp, httpclient);
        }
        return responseStr;
    }
    
    /**
     * Post方式提交,忽略URL中包含的参数,解决SSL双向数字证书认证
     *
     * @param url 提交地址
     * @param params 提交参数集, 键/值对
     * @param charset 参数编码集
     * @param keystoreUrl 密钥存储库路径
     * @param keystorePassword 密钥存储库访问密码
     * @param truststoreUrl 信任存储库绝路径
     * @param truststorePassword 信任存储库访问密码, 可为null
     * @return 响应消息
     * @throws NetServiceException
     */
    public String post(String url, Map<String, String> params, String charset, final URL keystoreUrl, final String keystorePassword, final URL truststoreUrl, final String truststorePassword)
    {
        if (url == null || StringUtils.isEmpty(url))
        {
            return null;
        }
        CloseableHttpClient httpclient = getHttpClient(charset);
        UrlEncodedFormEntity formEntity = null;
        try
        {
            if (charset == null || StringUtils.isEmpty(charset))
            {
                formEntity = new UrlEncodedFormEntity(getParamsList(params));
            }
            else
            {
                formEntity = new UrlEncodedFormEntity(getParamsList(params), charset);
            }
        }
        catch (UnsupportedEncodingException e)
        {
            LOG.error("不支持的编码集", e);
        }
        HttpPost hp = null;
        String responseStr = null;
        try
        {
            KeyStore keyStore = createKeyStore(keystoreUrl, keystorePassword);
            KeyStore trustStore = createKeyStore(truststoreUrl, keystorePassword);
            SSLConnectionSocketFactory socketFactory = SSLConnectionSocketFactory.getSocketFactory();
            // Registry scheme = new Registry(SSL_DEFAULT_SCHEME, socketFactory, SSL_DEFAULT_PORT);
            // httpclient.getConnectionManager().getSchemeRegistry().register(scheme);
            hp = new HttpPost(url);
            hp.setEntity(formEntity);
            responseStr = (String)httpclient.execute(hp, responseHandler);
        }
        catch (NoSuchAlgorithmException e)
        {
            LOG.error("指定的加密算法不可用", e);
        }
        catch (KeyStoreException e)
        {
            LOG.error("keytore解析异常", e);
        }
        catch (CertificateException e)
        {
            LOG.error("信任证书过期或解析异常", e);
        }
        catch (FileNotFoundException e)
        {
            LOG.error("keystore文件不存在", e);
        }
        catch (IOException e)
        {
            LOG.error("I/O操作失败或中断 ", e);
        }
        // catch (UnrecoverableKeyException e)
        // {
        // LOG.error("keystore中的密钥无法恢复异常", e);
        // }
        // catch (KeyManagementException e)
        // {
        // LOG.error("处理密钥管理的操作异常", e);
        // }
        finally
        {
            abortConnection(hp, httpclient);
        }
        return responseStr;
    }
    
    /*    *//**
             * 获取DefaultHttpClient实例
             *
             * @param charset 参数编码集, 可空
             * @return DefaultHttpClient 对象
             *//*
                * private DefaultHttpClient getDefaultHttpClient(final String charset)
                * {
                * DefaultHttpClient httpclient = new DefaultHttpClient();
                * httpclient.getParams().setParameter(CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_1);
                * // 模拟浏览器，解决一些服务器程序只允许浏览器访问的问题
                * httpclient.getParams().setParameter(CoreProtocolPNames.USER_AGENT,
                * "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)");
                * httpclient.getParams().setParameter(CoreProtocolPNames.USE_EXPECT_CONTINUE, Boolean.FALSE);
                * httpclient.getParams().setParameter(CoreProtocolPNames.HTTP_CONTENT_CHARSET, charset == null ?
                * CHARSET_GBK : charset);
                * httpclient.setHttpRequestRetryHandler(retryHandler);
                * return httpclient;
                * }
                */
    
    /**
     * 释放HttpClient连接
     *
     * @param hrb 请求对象
     * @param httpclient client对象
     */
    private void abortConnection(final HttpRequestBase hrb, final HttpClient httpclient)
    {
        if (hrb != null)
        {
            hrb.abort();
        }
        if (httpclient != null)
        {
            httpclient.getConnectionManager().shutdown();
        }
    }
    
    /**
     * 从给定的路径中加载此 KeyStore
     *
     * @param url keystore URL路径
     * @param password keystore访问密钥
     * @return keystore 对象
     */
    private KeyStore createKeyStore(final URL url, final String password)
        throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException
    {
        if (url == null)
        {
            throw new IllegalArgumentException("Keystore url may not be null");
        }
        KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
        InputStream is = null;
        try
        {
            is = url.openStream();
            keystore.load(is, password != null ? password.toCharArray() : null);
        }
        finally
        {
            if (is != null)
            {
                is.close();
                is = null;
            }
        }
        return keystore;
    }
    
    /**
     * 将传入的键/值对参数转换为NameValuePair参数集
     *
     * @param paramsMap 参数集, 键/值对
     * @return NameValuePair参数集
     */
    private List<NameValuePair> getParamsList(Map<String, String> paramsMap)
    {
        if (paramsMap == null || paramsMap.size() == 0)
        {
            return null;
        }
        List<NameValuePair> params = Lists.newArrayList();
        for (Map.Entry<String, String> map : paramsMap.entrySet())
        {
            params.add(new BasicNameValuePair(map.getKey(), map.getValue()));
        }
        return params;
    }
    
    /**
     * 通过ContentType 为json的格式进行http传输
     * 
     * @param url 远程url
     * @param content 传输内容
     * @return 出错为null或者空字符串
     */
    public String post(String url, String content)
    {
        String info = "";
        long start = System.currentTimeMillis();
        HttpPost httpPost = new HttpPost(url);
        
        try
        {
            if (content != null && content.length() > 0)
            {
                httpPost.setEntity(new StringEntity(content, ContentType.APPLICATION_JSON));
            }
            CloseableHttpClient httpclient = getHttpClient(CHARSET_UTF8);
            HttpResponse response = httpclient.execute(httpPost);
            HttpEntity entity = response.getEntity();
            if (entity == null)
            {
                return null;
            }
            info = EntityUtils.toString(entity, "UTF-8");
        }
        catch (Exception e)
        {
            LOG.error("postData Exception url: {}" + url, e);
            return null;
        }
        finally
        {
            httpPost.releaseConnection();
            long interval = System.currentTimeMillis() - start;
            LOG.debug("请求url:{" + url + "} ;耗时：{" + interval + "} ");
        }
        return info;
    }
    
    public static void main(String[] args)
    {
        Map<String, String> params = new HashMap<String, String>();
        params.put("ip", "103.238.107.104");
        String result = HttpClientUtils.getInstance().post("http://ip.taobao.com/service/getIpInfo.php", params);
        JSONObject json = JSONObject.parseObject(result);
        System.out.println(json);
        System.out.println(json.getJSONObject("data").getString("country") 
            + " " + json.getJSONObject("data").getString("region")
            + " " + json.getJSONObject("data").getString("city")
            + " " + json.getJSONObject("data").getString("isp")
            );
    }
}
